﻿//////////////////////////////////////////////////////////////////////////////
//
// Copyright © 1998-2024 Glodon Company Limited.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the “Software”),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////
#include "CmdBooleanHole.h"
#include "CommandIds.h"
#include "FilePath.h"
#include "GbmpFileSystem.h"
#include "CommandRegister.h"
#include "IMesh.h"
#include "ILine2d.h"
#include "ILine3d.h"
#include "IArc2d.h"
#include "IPolyCurve.h"
#include "IPolygon.h"
#include "ICurve3d.h"
#include "IBody.h"
#include "IDirectShape.h"
#include "AlgorithmMeshOperate.h"
#include "GmBodyBuilder.h"
#include "UiDocumentViewUtils.h"
#include "IUserTransaction.h"
#include "GcmpBuiltInCategoryUniIdentities.h"
#include "IGraphicsMeshBody.h"
#include "UiCommonDialog.h"
#include "IPickFilter.h"
#include "IPickTarget.h"
#include "InteractivePicker.h"
#include "IGraphicsNodeReference.h"
#include "IElementModelShape.h"
#include "IElement.h"
#include "IElementBasicInformation.h"
#include "IPolyCurve3d.h"
#include "ITrimmedSurface.h"
#include "IFace.h"
#include "IGraphicsStyle.h"
#include "IGraphicsStyleData.h"
#include "IGraphicsStyleManager.h"
#include "IGraphicsNodeStyleAndMaterialOverride.h"
#include "IUiView.h"
#include "IGraphicsCurve3d.h"
#include "IDocument.h"
#include "IGraphicsElementShape.h"
#include "IGenericElement.h"
#include <numeric>
#include "EnableCompileWarning_The_LAST_IncludeInCpp.h" 


using namespace Sample;
using namespace gcmp;

REGISTER_COMMAND(CmdBooleanHole)



CmdBooleanHole::CmdBooleanHole() : CommandBase(ID_CMD_BOOLEAN_HOLE)
{

}

CmdBooleanHole::~CmdBooleanHole()
{

}

bool CmdBooleanHole::IsEnabled() const
{
    return UiDocumentViewUtils::GetCurrentDocument() != nullptr;
}

bool CmdBooleanHole::IsVisible() const
{
    return UiDocumentViewUtils::GetCurrentDocument() != nullptr;
}

namespace
{
    std::wstring GetMeshBoolErrorCodeToMessage(MeshBoolErrorCode errorCode)
    {
        switch (errorCode)
        {
        case gcmp::MeshBoolErrorCode::BadParameter:
            return L"输入参数有误";
        case gcmp::MeshBoolErrorCode::Success:
            return L"Bool运算成功";
        case gcmp::MeshBoolErrorCode::Unknown:
            return L"未知的情况";
        case gcmp::MeshBoolErrorCode::MeshNullPtr:
            return L"Mesh指针为空";
        case gcmp::MeshBoolErrorCode::BoolOperationError:
            return L"Bool操作类型输入错误";
        case gcmp::MeshBoolErrorCode::DegenerateTriangle:
            return L"存在退化三角形";
        case gcmp::MeshBoolErrorCode::NextEdgeEmpty:
            return L"下一条边为空导致成环失败";
        case gcmp::MeshBoolErrorCode::FailCalcNextEdge:          
            return L"当前边和下一条边内外关系错误导致成环失败";
        case gcmp::MeshBoolErrorCode::FailCalcTriangulation:
            return L"三角剖分失败导致成体失败";
        case gcmp::MeshBoolErrorCode::IndexError:
            return L"顶点映射指针为空";
        case gcmp::MeshBoolErrorCode::NotClosed:
            return L"非封闭mesh，有重合顶点";
        case gcmp::MeshBoolErrorCode::FailCalcIntersectLine:
            return L"计算网格交线错误";
        case gcmp::MeshBoolErrorCode::FailCutMesh:
            return L"沿网格交线切割网格错误";
        case gcmp::MeshBoolErrorCode::FailCalssifyMesh:
            return L"对切割后的网格分类错误";
        case gcmp::MeshBoolErrorCode::OtherError:
            return L"其他错误";
        default:
            return L"未知";
        }      
    }
}

namespace
{
    class PickElementFilter : public gcmp::IPickFilter
    {
    public:

        virtual bool AllowElement(const gcmp::ElementId& id) const override
        {
            IDocument* pDocument = UiDocumentViewUtils::GetCurrentDocument();
            DBG_WARN_AND_RETURN_FALSE_UNLESS(pDocument, L"pDocument为空", L"GDMP", L"2023-11-08");
            IGenericElement* pSelElement = quick_cast<IGenericElement>(pDocument->GetElement(id));
            if (pSelElement == nullptr)
                return false;
            IElementBasicInformation* pElementBasicInformation = pSelElement->GetBasicInformation();
            if (pElementBasicInformation == nullptr)
                return false;
            if (pElementBasicInformation->GetCategoryUid() != BuiltInCategoryUniIdentities::BICU_FORM)
                return false;
            return true;
        }


        virtual bool AllowGraphicsNode(const gcmp::IGraphicsNodeReference& nodeReference) const override
        {
            return true;
        }

        virtual bool SetPickTargetOption(gcmp::IPickTarget* pickTarget) override
        {
            DBG_WARN_AND_RETURN_FALSE_UNLESS(pickTarget, L"pickTarget为空", L"GDMP", L"2023-09-30");
            pickTarget->DisableAll();
            pickTarget->EnableGraphicsElementShape();
            return true;
        }
    };

    //由一些连续线段的端点沿normal平移后，共同生成mesh
    gcmp::OwnerPtr<gcmp::IMesh> CreateSplitMesh(std::vector<MeshMeshIntSegment> curves, Vector3d normal)
    {
        std::vector<Vector3d> vertices;
        for (auto&& curve3d : curves)
        {
            Vector3d moveStartPoint = curve3d.StartPoint + normal;
            Vector3d moveEndPoint = curve3d.EndPoint + normal;
            Vector3d moveStartPoint1 = curve3d.StartPoint - normal;
            Vector3d moveEndPoint1 = curve3d.EndPoint - normal;

            vertices.emplace_back(moveStartPoint);
            vertices.emplace_back(moveEndPoint);
            vertices.emplace_back(moveStartPoint1);

            vertices.emplace_back(moveStartPoint1);
            vertices.emplace_back(moveEndPoint1);
            vertices.emplace_back(moveEndPoint);
        }

        std::vector<int> vertexIndices(vertices.size());
        std::iota(vertexIndices.begin(), vertexIndices.end(), 0);
        gcmp::OwnerPtr<gcmp::IMesh> opSplitMesh = gcmp::IMesh::Create(vertices, {}, {}, vertexIndices, {}, {});
        return TransferOwnership(opSplitMesh);
    }

    //计算ShellMesh裁剪
    gcmp::OwnerPtr<gcmp::IMesh> ShellMeshSplitShellMesh(gcmp::IMesh* pSplitMesh, IMesh* pShellMesh, bool isGetUp)
    {
        if (pSplitMesh == nullptr || pShellMesh == nullptr)
            return nullptr;
        std::vector<gcmp::OwnerPtr<gcmp::IMesh>> outUpMeshes;
        std::vector<gcmp::OwnerPtr<gcmp::IMesh>> outDownMeshes;
        gcmp::MeshBoolErrorCode errorCode = gcmp::AlgorithmMeshOperate::ShellMeshSplitShellMesh(pSplitMesh, pShellMesh, outUpMeshes, outDownMeshes);

        if (errorCode != gcmp::MeshBoolErrorCode::Success || outUpMeshes.size() + outDownMeshes.size() != 2)
            return nullptr;
        for (auto&& opDownMesh : outDownMeshes)
            outUpMeshes.emplace_back(TransferOwnership(opDownMesh));
        auto&& outMesh = outUpMeshes.front();
        DBG_WARN_AND_RETURN_FALSE_UNLESS(outMesh != nullptr, L"内部操作失败", L"GDMP", L"2023-09-30");
        auto&& outMesh1 = outUpMeshes.back();
        DBG_WARN_AND_RETURN_FALSE_UNLESS(outMesh != nullptr, L"内部操作失败", L"GDMP", L"2023-09-30");
        if (outMesh->GetBox().GetMaxPoint().Z() > outMesh1->GetBox().GetMaxPoint().Z())
        {
            auto&& intersectMesh = isGetUp ? outMesh : outMesh1;
            return TransferOwnership(intersectMesh);
        }
        else
        {
            auto&& intersectMesh = isGetUp ? outMesh1 : outMesh;
            return TransferOwnership(intersectMesh);
        }
    }
    //计算坑的侧面（和底面）形成的壳体mesh
    std::vector<gcmp::OwnerPtr<gcmp::IMesh>> CompleteIntersectMesh(IBody* pLoftBody, IMesh* pSelMesh)
    {
        std::vector<gcmp::OwnerPtr<gcmp::IMesh>> result;
        if (pLoftBody == nullptr || pSelMesh == nullptr)
            return result;
        int faceCount = pLoftBody->GetFaceCount();
        for (int j = 0; j < faceCount; j++)
        {
            const IFace* pFace = pLoftBody->GetFace(j);
            DBG_WARN_AND_RETURN_UNLESS(nullptr != pFace, result, L"内部操作失败", L"GDMP", L"2023-09-30");
            const ITrimmedSurface* pTSurface = pFace->GetTrimmedSurface();
            DBG_WARN_AND_RETURN_UNLESS(pTSurface != nullptr, result, L"内部操作失败", L"GDMP", L"2023-09-30");
            gcmp::OwnerPtr<gcmp::ISurface> opSurface = pTSurface->GetUnderlyingSurface();
            DBG_WARN_AND_RETURN_UNLESS(opSurface != nullptr, result, L"内部操作失败", L"GDMP", L"2023-09-30");
            Vector3d normal = opSurface->GetNormal(0, 0);
            //由face生成壳体再生成meshBody
            OwnerPtr<gcmp::IBody> opShellBody = gcmp::GmBodyBuilder::CreateShell(pFace);
            DBG_WARN_AND_RETURN_UNLESS(opShellBody != nullptr, result, L"内部操作失败", L"GDMP", L"2023-09-30");
            OwnerPtr<gcmp::IMesh> opShellBodyMesh;
            double edgeLengthRatio = 0.0;
            MeshFromBrepBodyErrorCode meshErrorCode = gcmp::AlgorithmMeshOperate::CreateMeshFromBrepBody(opShellBody.get(), 3, 1.5, edgeLengthRatio, gcmp::DiscreteEpsilonType::Default, opShellBodyMesh);
            DBG_WARN_AND_RETURN_UNLESS(opShellBodyMesh != nullptr && meshErrorCode == MeshFromBrepBodyErrorCode::Success, result, L"内部操作失败", L"GDMP", L"2023-09-30");

            //计算与大地的相交线（坑口的边线）
            std::vector<std::pair<std::vector<MeshMeshIntSegment>, bool>> intersectSegments;
            MeshMeshIntErrorCode errorCode = AlgorithmMeshOperate::MeshMeshIntersect(pSelMesh, opShellBodyMesh.get(), ReConstructMeshType::No, intersectSegments);
            DBG_WARN_AND_RETURN_UNLESS(errorCode == MeshMeshIntErrorCode::Success, result, L"内部操作失败", L"GDMP", L"2023-09-30");

            if (intersectSegments.size() == 0)//找出坑的底面(只有未挖穿才有需要底面)
            {
                normal.Normalize();
                if (normal.GetZ() < 0.5 && normal.GetZ() > -0.5)//必须是底面，不能是侧面
                    continue;
                if (opShellBodyMesh->GetBox().GetMinPoint().Z() > pSelMesh->GetBox().GetMaxPoint().Z())//没有碰到，整个在大地上面
                    continue;
                if (opShellBodyMesh->GetBox().GetMaxPoint().Z() < pSelMesh->GetBox().GetMinPoint().Z())//没有碰到，整个在大地下面
                    continue;
                if (pFace->GetOrientation() && normal.GetZ() > 0)continue;
                if ((!pFace->GetOrientation()) && normal.GetZ() < 0)continue;//寻找底面
                result.emplace_back(TransferOwnership(opShellBodyMesh));
            }
            else if (intersectSegments.size() == 1)//求出坑的侧面（未挖穿）:由坑口的边线沿face面法向生成新的SplitMesh，SplitMesh将opShellBodyMesh分出上下两部分，选择下部分
            {
                auto&& intersectSegment = intersectSegments.front();
                gcmp::OwnerPtr<gcmp::IMesh> opSplitMesh = CreateSplitMesh(intersectSegment.first, normal);

                gcmp::OwnerPtr<gcmp::IMesh> opIntersectMesh = ShellMeshSplitShellMesh(opSplitMesh.get(), opShellBodyMesh.get(), false);
                result.emplace_back(TransferOwnership(opIntersectMesh));
            }
            else if (intersectSegments.size() == 2)//求出坑的侧面（挖贯穿）
            {
                auto&& intersectSegment = intersectSegments.front();
                gcmp::OwnerPtr<gcmp::IMesh> opSplitMesh = CreateSplitMesh(intersectSegment.first, normal);
                auto&& intersectSegment1 = intersectSegments.back();
                gcmp::OwnerPtr<gcmp::IMesh> opSplitMesh1 = CreateSplitMesh(intersectSegment1.first, normal);
                gcmp::OwnerPtr<gcmp::IMesh> opUpSplitMesh;
                gcmp::OwnerPtr<gcmp::IMesh> opDownSplitMesh;
                if (opSplitMesh->GetBox().GetMinPoint().Z() > opSplitMesh1->GetBox().GetMinPoint().Z())
                {
                    opUpSplitMesh = TransferOwnership(opSplitMesh);
                    opDownSplitMesh = TransferOwnership(opSplitMesh1);
                }
                else
                {
                    opUpSplitMesh = TransferOwnership(opSplitMesh1);
                    opDownSplitMesh = TransferOwnership(opSplitMesh);
                }
                gcmp::OwnerPtr<gcmp::IMesh> opIntersectMesh = ShellMeshSplitShellMesh(opUpSplitMesh.get(), opShellBodyMesh.get(), false);
                gcmp::OwnerPtr<gcmp::IMesh> opIntersectMesh1 = ShellMeshSplitShellMesh(opDownSplitMesh.get(), opIntersectMesh.get(), true);
                double z = opIntersectMesh1->GetBox().GetMaxPoint().Z();
                result.emplace_back(TransferOwnership(opIntersectMesh1));
            }
        }
        return result;
    }

    //创建坑的body(放样)
    //bodySidelength::坑的相关尺寸
    OwnerPtr<IBody> CreateLoftBody(double bodySidelength,double bodyHeight)
    {     
        // 截面坐标系
        std::vector<Coordinate3d> planes;
        planes.emplace_back(Coordinate3d(Vector3d(0.0, 0.0,0), Vector3d(1.0, 0.0, 0.0), Vector3d(0.0, 1.0, 0.0)));
        planes.emplace_back(Coordinate3d(Vector3d(0.0, 0.0, bodyHeight*1.5), Vector3d(1.0, 0.0, 0.0), Vector3d(0.0, 1.0, 0.0)));
        //第一组多段线
        std::vector<Vector2d> sectionPts = {Vector2d(-bodySidelength, -bodySidelength), Vector2d(bodySidelength, -bodySidelength),Vector2d(bodySidelength, bodySidelength), Vector2d(-bodySidelength, bodySidelength),Vector2d(-bodySidelength, -bodySidelength) };
        OwnerPtr<IPolyCurve> opPolyCurve = IPolyCurve::Create(sectionPts);
        DBG_WARN_AND_RETURN_NULLPTR_UNLESS(opPolyCurve, L"opPolyCurve为空", L"GDMP", L"2023-09-30");
        OwnerPtr<IPolygon> opPolygon = IPolygon::Create();
        DBG_WARN_AND_RETURN_NULLPTR_UNLESS(opPolygon, L"opPolygon为空", L"GDMP", L"2023-09-30");
        opPolygon->AddPolyCurveByClone(opPolyCurve.get());
        //第二组多段线
        std::vector<Vector2d> sectionPts1 = { Vector2d(-bodySidelength, -bodySidelength), Vector2d(bodySidelength, -bodySidelength),Vector2d(bodySidelength, bodySidelength), Vector2d(-bodySidelength, bodySidelength),Vector2d(-bodySidelength, -bodySidelength) };
        opPolyCurve = IPolyCurve::Create(sectionPts1);
        DBG_WARN_AND_RETURN_NULLPTR_UNLESS(opPolyCurve, L"opPolyCurve为空", L"GDMP", L"2023-09-30");
        OwnerPtr<IPolygon> opPolygon1 = IPolygon::Create();
        DBG_WARN_AND_RETURN_NULLPTR_UNLESS(opPolygon1, L"opPolygon1 为空", L"GDMP", L"2023-09-30");
        opPolygon1->AddPolyCurveByClone(opPolyCurve.get());

        std::vector<const IPolygon*> sections =
        {
            opPolygon.get(),
            opPolygon1.get(),
        };
        OwnerPtr<IMultiSectionLoftFaceToPathProfileIndexMap> opMultiSectionLoftFaceToPathProfileIndexMap;
        MultiSectionLoftErrorCode errorCode;
        OwnerPtr<IBody> opLoftBody = GmBodyBuilder::CreateMultiSectionLoftBody(nullptr, planes, sections, MultiSectionLoftVertexMatchMode::Ratio, true, 1, Constants::LENGTH_EPS, &errorCode, opMultiSectionLoftFaceToPathProfileIndexMap);;
        DBG_WARN_AND_RETURN_NULLPTR_UNLESS(opLoftBody && (errorCode == MultiSectionLoftErrorCode::Success), L"内部处理未成功", L"GDMP", L"2023-09-30");
        return TransferOwnership(opLoftBody);
    }
    //从相交的壳体mesh拿出boundarys，再生成DirectShape用于展示
    bool CreateBoundaryDirectShape(gcmp::IMesh* pIntersectMesh, IDocument* pDocument)
    {
        if (pIntersectMesh==nullptr)  return false;
        DBG_WARN_AND_RETURN_FALSE_UNLESS(nullptr != pDocument, L"pDocument为null", L"GDMP", L"2023-09-30");

        //显示坑面的boundary
        gcmp::IDirectShape* pDirectShape = gcmp::IDirectShape::Create(pDocument, ::gcmp::BuiltInCategoryUniIdentities::BICU_FORM);
        DBG_WARN_AND_RETURN_FALSE_UNLESS(nullptr != pDirectShape, L"内部操作失败", L"GDMP", L"2023-09-30");
        gcmp::OwnerPtr<IGraphicsElementShape> opShape = IGraphicsElementShape::Create(gcmp::GraphicsRenderLayer::Model);
        DBG_WARN_AND_RETURN_FALSE_UNLESS(nullptr != opShape, L"内部操作失败", L"GDMP", L"2023-09-30");
        std::vector<std::vector<int>> boundarys = pIntersectMesh->GetBoundarys();
        const std::vector<Vector3d>& intersectVertices = pIntersectMesh->GetVertices();
        for (const std::vector<int>& boundary : boundarys)
        {
            for (int i = 0; i < boundary.size(); i++)
            {
                int startIndex = boundary[i];
                int endIndex = boundary[(i + 1) % boundary.size()];
                OwnerPtr<ILine3d> opLine = ILine3d::Create(intersectVertices[startIndex], intersectVertices[endIndex]);
                if (opLine == nullptr)continue;
                opShape->AddChild(IGraphicsCurve3d::Create(opLine.get()));
            }
        }
        pDirectShape->SetGraphicsElementShape(TransferOwnership(opShape));      
        return true;
    }
}

gcmp::OwnerPtr<gcmp::IAction> CmdBooleanHole::ExecuteCommand(const gcmp::CommandParameters& cmdParams)
{
    IDocument* pDocument = UiDocumentViewUtils::GetCurrentDocument();
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(pDocument, L"pDocument为空", L"GDMP", L"2023-09-30");

    //1.拾取mesh对象
    const bool isMultiSelect = false;
    const bool isPickPointSelection = true;
    const std::wstring mouseTooltip = L"选择MeshBody对象，尝试从MeshBody上方中间处挖个洞";
    const std::wstring statusBarTooltip = L"选择MeshBody对象，尝试从MeshBody上方中间处挖个洞";
    Vector3d mousePoint;
    GraphicsNodeReferenceOwnerPtrVector allSelections;
    OwnerPtr<PickElementFilter> pickfilter(NEW_AS_OWNER_PTR(PickElementFilter));
    auto pickResult = InteractivePicker::Select(isMultiSelect, isPickPointSelection, TransferOwnership(pickfilter), mouseTooltip, statusBarTooltip, mousePoint, allSelections);
    if (pickResult.FinishStatus != InteractivePicker::PickerFinishStatus::Successful || allSelections.size() == 0)
        return nullptr;

    //2.统计mesh对象的最大boundingBox,用于计算需要挖的坑的尺寸和位置,并记录mesh对象的mesh数据用于布尔计算
    Vector3d maxPoint = Vector3d::Create(-Constants::MAX_MODEL_SPACE_SIZE, -Constants::MAX_MODEL_SPACE_SIZE, -Constants::MAX_MODEL_SPACE_SIZE);//用于记录整个大地的最大位置点
    Vector3d minPoint = Vector3d::Create(Constants::MAX_MODEL_SPACE_SIZE, Constants::MAX_MODEL_SPACE_SIZE, Constants::MAX_MODEL_SPACE_SIZE);//用于记录整个大地的最小位置点
    auto&& opNodeRefOwner = allSelections[0];
    if (opNodeRefOwner == nullptr) return nullptr;
    IElement* pSelElement = pDocument->GetElement(opNodeRefOwner->GetElementId());
    if (pSelElement == nullptr) return nullptr;
    gcmp::IElementModelShape* pModelShape = pSelElement->GetElementModelShape();
    if (pModelShape == nullptr) return nullptr;
    const gcmp::IGraphicsElementShape* pElementShape = pModelShape->GetGraphicsElementShape();
    if (pElementShape == nullptr) return nullptr;
    const std::vector<OwnerPtr<IGraphicsNode>>& nodes = pElementShape->GetChildren();
    const IMesh* pSelMesh;
    for (auto&& graphicsNode : nodes)
    {
        if (const IGraphicsMeshBody* graphicsMeshBody = quick_cast<IGraphicsMeshBody>(graphicsNode.get()))
        {
            pSelMesh = graphicsMeshBody->GetMesh();
            if (pSelMesh == nullptr || !pSelMesh->IsClosed()) continue;//非闭合mesh不支持布尔运算              
            if (pSelMesh->GetBox().GetMaxPoint().GetZ() > maxPoint.GetZ())
            {
                maxPoint = pSelMesh->GetBox().GetMaxPoint();
            }
            if (pSelMesh->GetBox().GetMaxPoint().GetZ() < minPoint.GetZ())
                minPoint = pSelMesh->GetBox().GetMinPoint();
            break;
        }
    }
    if (pSelMesh == nullptr)
        return nullptr;

    //3.创建坑的body(放样)  
    double bodySidelength = (maxPoint.GetY() - minPoint.GetY()) / 20;
    double bodyHeight = maxPoint.GetZ() - minPoint.GetZ();
    OwnerPtr<IBody> opLoftBody = CreateLoftBody(bodySidelength, bodyHeight);
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(opLoftBody, L"内部处理未成功", L"GDMP", L"2023-09-30");
    double maxMeshX = maxPoint.X() - minPoint.X();
    double maxMeshZ = maxPoint.Z() - minPoint.Z();
    Matrix4d translate;
    translate.MakeTranslate((maxPoint + minPoint) / 2);//在mesh中间挖洞
    opLoftBody->Transform(translate);

    //4.计算生成的坑各个面的边界线（只关心挖隧道布尔计算这里可以忽略）
    OwnerPtr<IUserTransaction> opUserTransaction = IUserTransaction::Create(pDocument, GBMP_TR(L"创建ModelCurves"));
    OwnerPtr<IMesh> opMesh = TransferOwnershipCast<IMesh>(pSelMesh->Clone());
    std::vector<gcmp::OwnerPtr<gcmp::IMesh>> opIntersectMeshs = CompleteIntersectMesh(opLoftBody.get(), opMesh.get());
    for (auto&& intersectMesh : opIntersectMeshs)
        CreateBoundaryDirectShape(intersectMesh.get(), pDocument);
    opUserTransaction->Commit();

    // 5.坑body生成坑mesh
    OwnerPtr<gcmp::IMesh> opLoftMesh;
    double edgeLengthRatio = 0.0;
    MeshFromBrepBodyErrorCode meshErrorCode = gcmp::AlgorithmMeshOperate::CreateMeshFromBrepBody(opLoftBody.get(), 2, 1.5, edgeLengthRatio, gcmp::DiscreteEpsilonType::User, opLoftMesh);
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(opLoftMesh, L"opLoftMesh为空", L"GDMP", L"2023-09-30");
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(meshErrorCode == MeshFromBrepBodyErrorCode::Success, L"内部操作失败", L"GDMP", L"2023-09-30");
    OwnerPtr<IUserTransaction> opUserTransaction1 = IUserTransaction::Create(pDocument, GBMP_TR(L"创建Mesh"));
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(opUserTransaction1, L"opUserTransaction1创建失败", L"GDMP", L"2023-09-30");

    //5.坑mesh与大地布尔计算，并显示   
    OwnerPtr<IMesh> opSubtractMesh;
    MeshBoolErrorCode boolErrorCode = AlgorithmMeshOperate::MeshBoolSubtract(pSelMesh, opLoftMesh.get(), opSubtractMesh);
    if (!opSubtractMesh || boolErrorCode != MeshBoolErrorCode::Success)
    {
        std::wstring errorMessage = GetMeshBoolErrorCodeToMessage(boolErrorCode);
        UiCommonDialog::ShowMessageBox(L"提示", gcmp::StringUtil::FormatWString(L"挖洞失败,mesh布尔运算失败:%s.", GetMeshBoolErrorCodeToMessage(boolErrorCode).c_str()), (int)UiCommonDialog::ButtonType::Yes);
        return  nullptr;
    }
    gcmp::OwnerPtr<IGraphicsElementShape> opShape = IGraphicsElementShape::Create(gcmp::GraphicsRenderLayer::Model);
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(opShape, L"opShape创建失败", L"GDMP", L"2023-09-30");
    opShape->AddChild(IGraphicsMeshBody::Create(TransferOwnership(opSubtractMesh)));
    pModelShape->SetGraphicsElementShape(TransferOwnership(opShape));
    opUserTransaction1->Commit();

    return nullptr;
}
